package com.lkd.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.base.Strings;
import com.lkd.common.VMSystem;
import com.lkd.config.TopicConfig;
import com.lkd.contract.SupplyChannel;
import com.lkd.contract.SupplyContract;
import com.lkd.contract.TaskCompleteContract;
import com.lkd.dao.TaskDao;
import com.lkd.emq.MqttProducer;
import com.lkd.entity.TaskDetailsEntity;
import com.lkd.entity.TaskEntity;
import com.lkd.entity.TaskStatusTypeEntity;
import com.lkd.exception.LogicException;
import com.lkd.feign.UserService;
import com.lkd.feign.VMService;
import com.lkd.http.vo.CancelTaskViewModel;
import com.lkd.http.vo.CollectTaskVo;
import com.lkd.http.vo.TaskDetailsViewModel;
import com.lkd.http.vo.TaskReportInfoVO;
import com.lkd.http.vo.TaskViewModel;
import com.lkd.http.vo.UserWorkTopVO;
import com.lkd.service.TaskDetailsService;
import com.lkd.service.TaskService;
import com.lkd.service.TaskStatusTypeService;
import com.lkd.vo.Pager;
import com.lkd.vo.UserVO;
import com.lkd.vo.UserWorkVO;
import com.lkd.vo.VmVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

@Service
@Slf4j
public class TaskServiceImpl extends ServiceImpl<TaskDao,TaskEntity> implements TaskService{

    @Autowired
    private TaskStatusTypeService statusTypeService;

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Autowired
    private VMService vmService;

    @Autowired
    private UserService userService;

    @Autowired
    private TaskDetailsService taskDetailsService;

    @Autowired
    private MqttProducer mqttProducer;

    @Autowired
    private TaskDao taskDao;


    @Override
    public Pager<TaskEntity> search(Long pageIndex, Long pageSize, String innerCode, Integer userId, String taskCode, Integer status, Boolean isRepair, String start, String end) {
        Page<TaskEntity> page = new Page<>(pageIndex,pageSize);
        LambdaQueryWrapper<TaskEntity> qw = new LambdaQueryWrapper<>();
        if(!Strings.isNullOrEmpty(innerCode)){
            qw.eq(TaskEntity::getInnerCode,innerCode);
        }
        if(userId != null && userId > 0){
            qw.eq(TaskEntity::getUserId,userId);
        }
        if(!Strings.isNullOrEmpty(taskCode)){
            qw.like(TaskEntity::getTaskCode,taskCode);
        }
        if(status != null && status > 0){
            qw.eq(TaskEntity::getTaskStatus,status);
        }
        if(isRepair != null){
            if(isRepair){
                qw.ne(TaskEntity::getProductTypeId, VMSystem.TASK_TYPE_SUPPLY);
            }else {
                qw.eq(TaskEntity::getProductTypeId,VMSystem.TASK_TYPE_SUPPLY);
            }
        }
        if(!Strings.isNullOrEmpty(start) && !Strings.isNullOrEmpty(end)){
            qw
                    .ge(TaskEntity::getCreateTime, LocalDate.parse(start, DateTimeFormatter.ISO_LOCAL_DATE))
                    .le(TaskEntity::getCreateTime,LocalDate.parse(end,DateTimeFormatter.ISO_LOCAL_DATE));
        }
        //根据最后更新时间倒序排序
        qw.orderByDesc(TaskEntity::getUpdateTime);

        return Pager.build(this.page(page,qw));
    }



    @Override
    public List<TaskStatusTypeEntity> getAllStatus() {
        QueryWrapper<TaskStatusTypeEntity> qw = new QueryWrapper<>();
        qw.lambda()
                .ge(TaskStatusTypeEntity::getStatusId,VMSystem.TASK_STATUS_CREATE);

        return statusTypeService.list(qw);
    }

    /**
     * <p>Description: 创建工单</p>
     *
     * @author liuyiming
     * @date 2022-12-16 20:31
     * @version 1.0.0
     */
    @Transactional
    @Override
    public Boolean createTask(TaskViewModel taskViewModel) {
    //保证事务，将工单插入操作和工单详情插入操作，放在同一个事务当中，要么同时成功，要么同时失败

        //1.填充工单表的数据
        VmVO vmInfo = vmService.getVMInfo(taskViewModel.getInnerCode());
        UserVO user = userService.getUser(taskViewModel.getUserId());
        if (Objects.isNull(vmInfo) || Objects.isNull(user)) {
            throw new LogicException("售货机/用户信息不存在");
        }

        //2.校验售货机的状态
        checkVmStatus(taskViewModel, vmInfo);

        //3.校验同一台售货机是否有未完成的同类型工单
        if (getTaskEntityQueryWrapper(taskViewModel)) {
            throw new LogicException("同一台售货机存在未完成的同类型工单");
        }

        //组装数据
        TaskEntity taskEntity = fillTaskInfo(taskViewModel, vmInfo, user);
        this.save(taskEntity);

        //4.如果是补货工单，需要插入工单详情表的数据
        if (taskViewModel.getProductType() == VMSystem.TASK_TYPE_SUPPLY) {
            insertTaskDetailData(taskViewModel, taskEntity);
        }
        //修改员工分数
        updateUserScore(vmInfo.getRegionId(),taskEntity.getProductTypeId(),user.getUserId(),1);

        return Boolean.TRUE;
    }

    @Override
    public boolean acceptTask(long taskId, Integer userId) {
        TaskEntity taskEntity = this.getById(taskId);
        if (null == taskEntity) throw new LogicException("未查询到该订单");
        //判断工单状态
        if (!taskEntity.getTaskStatus().equals(VMSystem.TASK_STATUS_CREATE)){
            throw new LogicException("当前工单状态不是待处理状态，不能接收");
        }
        if (!taskEntity.getUserId().equals(userId)){
            throw new LogicException("当前订单不属于你的订单，违法操作");
        }
        //设置已接单 状态：进行中
        taskEntity.setTaskStatus(VMSystem.TASK_STATUS_PROGRESS);
        this.updateById(taskEntity);
        return true;
    }

    @Override
    public Boolean cancelTask(long taskId, CancelTaskViewModel cancelTaskViewModel) {
        TaskEntity taskEntity = this.getById(taskId);
        if (null == taskEntity) throw new LogicException("未查询到该订单");
        if (taskEntity.getTaskStatus().equals(VMSystem.TASK_STATUS_CANCEL)
                || taskEntity.getTaskStatus().equals(VMSystem.TASK_STATUS_FINISH)){
            throw new LogicException("工单已结束");
        }
        if (taskEntity.getTaskStatus().equals(VMSystem.TASK_STATUS_PROGRESS)
            && cancelTaskViewModel.getUserId().equals(taskEntity.getAssignorId())){
            throw new LogicException("当前工单正在执行中，请勿取消");
        }

        if (!cancelTaskViewModel.getUserId().equals(taskEntity.getUserId())
                && !cancelTaskViewModel.getUserId().equals(taskEntity.getAssignorId())){
            throw new LogicException("当前操作非法");
        }

        //设置已取消工单 状态：已取消
        taskEntity.setTaskStatus(VMSystem.TASK_STATUS_CANCEL);
        taskEntity.setDesc(cancelTaskViewModel.getDesc());
        this.updateById(taskEntity);
        //修改员工分数
        updateUserScore(taskEntity.getRegionId(),taskEntity.getProductTypeId(),taskEntity.getUserId(),-1);
        return true;
    }

    //修改员工分数
    private void updateUserScore(Long regionId,Integer productTypeId,Integer userId,Integer score){
        String roleCode = VMSystem.REPAIRED_ROLE_CODE;
        if (Objects.equals(productTypeId,VMSystem.TASK_TYPE_SUPPLY)){
            roleCode = VMSystem.SUPPLY_ROLE_CODE;
        }
        //运维人员取消一个工单，扣除当前运维人员的分数
        //key：前缀_日期_区域_角色code
        String redisKey = VMSystem.REGION_TASK_KEY_PREF
                + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))
                + "." + regionId
                + "." + roleCode;
        //给指定的key、value加指定分数
        redisTemplate.opsForZSet().incrementScore(redisKey,userId,score);
    }

    @Override
    public Boolean completeTask(long taskId) {
        TaskEntity taskEntity = this.getById(taskId);
        if (null == taskEntity) throw new LogicException("未查询到该工单");
        if (!taskEntity.getTaskStatus().equals(VMSystem.TASK_STATUS_PROGRESS)){
            throw new LogicException("非法状态");
        }
        //设置已完成工单 状态：已完成
        taskEntity.setTaskStatus(VMSystem.TASK_STATUS_FINISH);
        this.updateById(taskEntity);

        //如果当前工单的类型是运维工单（投放、撤机），则发送运维工单到售货机服务，修改售货机状态
        if(taskEntity.getProductTypeId().equals(VMSystem.TASK_TYPE_DEPLOY)
            || taskEntity.getProductTypeId().equals(VMSystem.TASK_TYPE_REVOKE)){
            //把售货机编号和工单类型传进去，发送到售货机服务
            TaskCompleteContract taskCompleteContract = new TaskCompleteContract();
            taskCompleteContract.setInnerCode(taskEntity.getInnerCode());
            taskCompleteContract.setTaskType(taskEntity.getProductTypeId());
            try {
                mqttProducer.send(TopicConfig.VMS_COMPLETED_TOPIC,2,taskCompleteContract);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }

        //如果当前工单的类型是运营工单（补货），则发送运维工单到售货机服务，把补货的数量加上原本的数量并更新售货机表
        if (taskEntity.getProductTypeId().equals(VMSystem.TASK_TYPE_SUPPLY)){
            try {
                //查询出工单详情
                List<TaskDetailsEntity> detailsEntityList = taskDetailsService.getByTaskId(taskId);
                //循环出工单中要补的货的具体信息，然后把这些信息发送到售货机服务
                List<SupplyChannel> SupplyChannelLists = new ArrayList();
                for (TaskDetailsEntity taskDetailsEntity : detailsEntityList) {
                    SupplyChannel supplyChannel = new SupplyChannel();
                    supplyChannel.setChannelId(taskDetailsEntity.getChannelCode());
                    supplyChannel.setCapacity(taskDetailsEntity.getExpectCapacity());
                    SupplyChannelLists.add(supplyChannel);
                }
                SupplyContract supplyContract = new SupplyContract();
                supplyContract.setSupplyData(SupplyChannelLists);
                supplyContract.setInnerCode(taskEntity.getInnerCode());
                mqttProducer.send(TopicConfig.VMS_SUPPLY_TOPIC, 2, supplyContract);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        return true;
    }

    /**
     * 获取最少工单量用户
     *
     * @param regionId  区域id
     * @param isSupply  true 运营工单 false 运维工单
     * @return 用户id  if get nothing, return null
     */
    @Override
    public Integer getLeastUser(Long regionId, Boolean isSupply) {
        String roleCode = VMSystem.SUPPLY_ROLE_CODE;
        if(!isSupply){
            roleCode = VMSystem.REPAIRED_ROLE_CODE;
        }

        String redisKey = VMSystem.REGION_TASK_KEY_PREF
                + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))
                + "." + regionId
                + "." + roleCode;

        //range:取指定key的value,后边是区间，从第0位开始取，到第0位结束（取第0位上的，也就是第一个）
        Set<Object> range = redisTemplate.opsForZSet().range(redisKey, 0, 0);

        if (CollectionUtils.isEmpty(range)) {
            return null;
        }
        return (Integer) new ArrayList<>(range).get(0);
    }

    /**
     * 获取当时工单汇总信息
     */
    @Override
    public List<TaskReportInfoVO> taskReportInfo(LocalDateTime start, LocalDateTime end) {
        List<TaskReportInfoVO> taskReportInfoVOS = new ArrayList<>();

        //运维人员
        TaskReportInfoVO taskReportInfoVO = new TaskReportInfoVO();
        taskReportInfoVO.setTotal(countTask(false,start, end,null));    //工单总数
        taskReportInfoVO.setCompletedTotal(countTask(false,start, end,VMSystem.TASK_STATUS_FINISH));   //完成数
        taskReportInfoVO.setCancelTotal(countTask(false,start, end,VMSystem.TASK_STATUS_CANCEL));  //拒绝总数
        taskReportInfoVO.setProgressTotal(countTask(false,start, end,VMSystem.TASK_STATUS_PROGRESS));    //进行中总数
        taskReportInfoVO.setWorkerCount(userService.getOperatorCount());  //工作人数
        taskReportInfoVO.setRepair(false);   //是否是运维工单统计
        taskReportInfoVOS.add(taskReportInfoVO);

        //运营人员
        TaskReportInfoVO taskReportInfoVO2 = new TaskReportInfoVO();
        taskReportInfoVO2.setTotal(countTask(true,start, end,null));    //工单总数
        taskReportInfoVO2.setCompletedTotal(countTask(true,start, end,VMSystem.TASK_STATUS_FINISH));   //完成数
        taskReportInfoVO2.setCancelTotal(countTask(true,start, end,VMSystem.TASK_STATUS_CANCEL));  //拒绝总数
        taskReportInfoVO2.setProgressTotal(countTask(true,start, end,VMSystem.TASK_STATUS_PROGRESS));    //进行中总数
        taskReportInfoVO2.setWorkerCount(userService.getRepairerCount());  //工作人数
        taskReportInfoVO2.setRepair(true);   //是否是运维工单统计
        taskReportInfoVOS.add(taskReportInfoVO2);

        return taskReportInfoVOS;
    }

    /**
     * 人效排名月度统计
     * @param start
     * @param end
     * @param isRepair
     * @param regionId
     * @return
     */
    @Override
    public List<UserWorkTopVO> userWorkTop10(LocalDate start, LocalDate end, boolean isRepair, long regionId) {
        Page<UserWorkTopVO> page = new Page();
        page.setCurrent(1);
        page.setSize(10);
        page.setSearchCount(false);  //分页 1.查询这一页数据 2.查询总数据 totalCount 把totalCount禁用
        List<UserWorkTopVO> userWorkTopVOS = taskDao.userWorkTop10(page, start, end, isRepair, regionId);
        return userWorkTopVOS;
    }

    /**
     * 工单状态统计（柱状图）
     * @param startDate
     * @param endDate
     */
    @Override
    public List<CollectTaskVo> collectReport(LocalDate startDate, LocalDate endDate) {
        List<CollectTaskVo> collectTaskVos = new ArrayList<>();
        int days = Period.between(startDate, endDate.plusDays(1)).getDays();
        LocalDate start = startDate;
        for (int i = 0; i < days; i++) {
            //获取到date这一天日期
            CollectTaskVo collectTaskVo = new CollectTaskVo();
            collectTaskVos.add(collectTaskVo);
            collectTaskVo.setCollectDate(start.format(DateTimeFormatter.ISO_LOCAL_DATE));
            collectTaskVo.setProgressCount(countTask(VMSystem.TASK_STATUS_PROGRESS,start,start.plusDays(1)));
            collectTaskVo.setFinishCount(countTask(VMSystem.TASK_STATUS_FINISH,start,start.plusDays(1)));
            collectTaskVo.setCancelCount(countTask(VMSystem.TASK_STATUS_CANCEL,start,start.plusDays(1)));
            collectTaskVos.add(collectTaskVo);
            start = start.plusDays(1);
        }
        return collectTaskVos;
    }

    private int countTask(Integer status, LocalDate start, LocalDate end) {
        LambdaQueryWrapper<TaskEntity> queryWrapper = Wrappers.<TaskEntity>lambdaQuery();
        //1.加入开始和结束时间
        queryWrapper.ge(TaskEntity::getUpdateTime,start); //>=开始日期
        queryWrapper.lt(TaskEntity::getUpdateTime,end); //<结束日期
        //2.判断状态
        if (status != null) {
            queryWrapper.eq(TaskEntity::getTaskStatus, status);
        }
        return count(queryWrapper);
    }

    /**
     * 统计工单总数
     * @param isRepair  工单类型：true运营  false运维
     * @param start 起始时间
     * @param end   结束时间
     * @param status 工单状态
     * @return
     */
    private Integer countTask(boolean isRepair,LocalDateTime start, LocalDateTime end,Integer status) {
        LambdaQueryWrapper<TaskEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.between(TaskEntity::getUpdateTime,start,end);
        if (status != null) {
            wrapper.eq(TaskEntity::getTaskStatus,status);
        }
        //判断运维还是运营工单 运维工单 != 2  运营工单 = 2
        //非补货工单，全是运维
        if (isRepair){
            //ne:表示不等于（!=）
            wrapper.ne(TaskEntity::getProductTypeId,VMSystem.TASK_TYPE_SUPPLY);
        }else {
            wrapper.eq(TaskEntity::getProductTypeId,VMSystem.TASK_TYPE_SUPPLY);
        }
        return this.count(wrapper);
    }

    /**
     * 校验同一台售货机是否有未完成的同类型工单
     *
     * @param taskViewModel 工单创建入参
     * @return Boolean
     */
    private Boolean getTaskEntityQueryWrapper(TaskViewModel taskViewModel) {
        QueryWrapper<TaskEntity> objectQueryWrapper = new QueryWrapper();
        objectQueryWrapper.lambda().select(TaskEntity::getTaskId)
                .eq(TaskEntity::getInnerCode, taskViewModel.getInnerCode())
                .eq(TaskEntity::getProductTypeId, taskViewModel.getProductType())
                .le(TaskEntity::getTaskStatus, VMSystem.TASK_STATUS_PROGRESS);
        return this.count(objectQueryWrapper) > BigDecimal.ZERO.intValue();
    }

    /**
     * 校验售货机状态
     *
     * @param taskViewModel 工单创建入参
     * @param vmInfo        售货机信息
     */
    private void checkVmStatus(TaskViewModel taskViewModel, VmVO vmInfo) {
        if (taskViewModel.getProductType() == VMSystem.TASK_TYPE_DEPLOY
                && vmInfo.getVmStatus().equals(VMSystem.VM_STATUS_RUNNING)) {
            throw new LogicException("当前售货机已经是运营状态，请勿再次投放");
        }

        if (taskViewModel.getProductType() == VMSystem.TASK_TYPE_SUPPLY
                && !vmInfo.getVmStatus().equals(VMSystem.VM_STATUS_RUNNING)) {
            throw new LogicException("当前售货机不是运营状态，请勿补货");
        }

        if (taskViewModel.getProductType() == VMSystem.TASK_TYPE_REVOKE
                && !vmInfo.getVmStatus().equals(VMSystem.VM_STATUS_RUNNING)) {
            throw new LogicException("当前售货机不是运营状态，请勿撤机");
        }

    }

    /**
     * 插入工单详情数据
     *
     * @param taskViewModel 入参
     * @param taskEntity    工单表对象
     */
    private void insertTaskDetailData(TaskViewModel taskViewModel, TaskEntity taskEntity) {
        List<TaskDetailsViewModel> details = taskViewModel.getDetails();

        //第一种实现方式，循环遍历，进行插入操作
        details.forEach(each -> {
            TaskDetailsEntity taskDetailsEntity = new TaskDetailsEntity();
            taskDetailsEntity.setTaskId(taskEntity.getTaskId());
            BeanUtils.copyProperties(each, taskDetailsEntity);
            taskDetailsService.save(taskDetailsEntity);
        });

        //第二种实现方式,批量插入
//        List<TaskDetailsEntity> collect = details.stream().map(each -> {
//            TaskDetailsEntity taskDetailsEntity = new TaskDetailsEntity();
//            taskDetailsEntity.setTaskId(taskEntity.getTaskId());
//            BeanUtils.copyProperties(each, taskDetailsEntity);
//            return taskDetailsEntity;
//        }).collect(Collectors.toList());
//        taskDetailsService.saveBatch(collect);
    }

    /**
     * 填充工单的基础属性
     *
     * @param taskViewModel
     * @param vmInfo
     * @param user
     * @return
     */
    private TaskEntity fillTaskInfo(TaskViewModel taskViewModel, VmVO vmInfo, UserVO user) {
        //插入工单数据
        TaskEntity taskEntity = new TaskEntity();
        taskEntity.setTaskCode(generateTaskCode());
        taskEntity.setTaskStatus(VMSystem.TASK_STATUS_CREATE);
        taskEntity.setCreateType(taskViewModel.getCreateType());
        taskEntity.setInnerCode(taskViewModel.getInnerCode());
        taskEntity.setRegionId(vmInfo.getRegionId());
        taskEntity.setUserId(taskViewModel.getUserId());
        taskEntity.setUserName(user.getUserName());
        taskEntity.setDesc(taskViewModel.getDesc());
        taskEntity.setProductTypeId(taskViewModel.getProductType());
        taskEntity.setAssignorId(taskViewModel.getAssignorId());
        taskEntity.setAddr(vmInfo.getNodeAddr());
        return taskEntity;
    }


    /**
     * 生成工单编号
     * @return
     */
    private String generateTaskCode(){
        //日期+序号
        String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));  //日期字符串
        String key= "lkd.task.code."+date; //redis key
        Object obj = redisTemplate.opsForValue().get(key);
        if(obj==null){
            redisTemplate.opsForValue().set(key,1L, Duration.ofDays(1) );
            return date+"0001";
        }
        return date+  Strings.padStart( redisTemplate.opsForValue().increment(key,1).toString(),4,'0');
    }

}
